﻿using ScottPlot;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using UtilZ.Dotnet.Ex.Base;

namespace UtilZ.Dotnet.ScottPlotEx
{
    /// <summary>
    /// 瀑布图
    /// </summary>
    unsafe public class WaterfallPlot : IPlottable, IHasColorAxis, IDisposable
    {
        private IWaterfallPlot _waterfallPlot = null;
        private bool _disposed = false;




        private IColormap _colormap = null;
        /// <summary>
        /// 获取或设置颜色域
        /// </summary>
        public IColormap Colormap
        {
            get { return this._colormap; }
        }

        private ScottPlot.Range _dataRange;
        /// <summary>
        /// 获取数据范围
        /// </summary>
        /// <returns></returns>
        public ScottPlot.Range GetRange()
        {
            return this._dataRange;
        }

        private PlotMoveDirection _moveOrientation = PlotMoveDirection.Down;
        /// <summary>
        /// 移动方向
        /// </summary>
        public PlotMoveDirection MoveOrientation
        {
            get { return this._moveOrientation; }
        }

        private CoordinateRect _alignedExtent;
        private double _cellWidth;
        private double _cellHeight;
        private int _framCount;
        private int _fftLength;
        private int _imgPixs;

        /// <summary>
        /// 构造函数初始化
        /// </summary>
        public WaterfallPlot()
        {

        }

        /// <summary>
        /// 析构函数
        /// </summary>
        ~WaterfallPlot()
        {
            this.PrimitiveDispose();
        }

        /// <summary>
        /// 释放资源
        /// </summary>
        public void Dispose()
        {
            this.PrimitiveDispose();
        }

        private void PrimitiveDispose()
        {
            if (this._disposed)
            {
                return;
            }
            this._disposed = true;

            if (this._waterfallPlot != null)
            {
                this._waterfallPlot.Dispose();
                this._waterfallPlot = null;
            }
        }




        /// <summary>
        /// 设置瀑布图
        /// </summary>
        /// <param name="fftLength">数据宽带(移动方向的垂直方向时此值为FFT点数,否则为帧数)</param>
        /// <param name="framCount">数据高度(移动方向的水平方向时此值为FFT点数,否则为帧数)</param>
        /// <param name="min">频谱图中的最小值</param>
        /// <param name="max">频谱图中的最大值</param>
        /// <param name="colormap">色域</param>
        /// <param name="moveOrientation">瀑布图移动方向</param>
        /// <param name="options">可选项参数</param>
        /// <exception cref="ArgumentOutOfRangeException">参数无效异常</exception>
        /// <exception cref="ArgumentException">参数无效异常</exception>
        public void Set(int fftLength, int framCount, double min, double max, IColormap colormap, PlotMoveDirection moveOrientation, WaterfallPlotOptions options = null)
        {
            if (fftLength < PlotConstant.SIZE_MIN)
            {
                throw new ArgumentOutOfRangeException(nameof(fftLength), $"FFT数据长度不能小于{PlotConstant.SIZE_MIN}");
            }

            if (framCount < PlotConstant.SIZE_MIN)
            {
                throw new ArgumentOutOfRangeException(nameof(framCount), $"数据帧数不能小于{PlotConstant.SIZE_MIN}");
            }

            if (!DoubleEx.Valid(min))
            {
                throw new ArgumentException("最小值无效", nameof(min));
            }

            if (!DoubleEx.Valid(max))
            {
                throw new ArgumentException("最大值无效", nameof(min));
            }

            if (min > max)
            {
                throw new ArgumentException("最小值不能大于最大值");
            }

            if (moveOrientation != PlotMoveDirection.Up &&
                moveOrientation != PlotMoveDirection.Down &&
                moveOrientation != PlotMoveDirection.Left &&
                moveOrientation != PlotMoveDirection.Right)
            {
                throw new NotSupportedException($"不支持的移动方向{moveOrientation.ToString()}");
            }

            this._dataRange = new ScottPlot.Range(min, max);
            this._framCount = framCount;
            this._fftLength = fftLength;
            this._imgPixs = framCount * fftLength;

            if (options == null)
            {
                options = WaterfallPlotOptions.Default;
            }

            if (colormap == null)
            {
                colormap = new ScottPlot.Colormaps.Viridis();
            }
            this._colormap = colormap;

            if (this._waterfallPlot != null)
            {
                this._waterfallPlot.Dispose();
                this._waterfallPlot = null;
            }

            switch (moveOrientation)
            {
                case PlotMoveDirection.Up:
                    this._waterfallPlot = new WaterfallPlot_Up(fftLength, framCount, this._dataRange, colormap, options);
                    break;
                case PlotMoveDirection.Down:
                    this._waterfallPlot = new WaterfallPlot_Down(fftLength, framCount, this._dataRange, colormap, options);
                    break;
                case PlotMoveDirection.Left:
                case PlotMoveDirection.Right:
                    this._waterfallPlot = new WaterfallPlot_Horizontally(fftLength, framCount, this._dataRange, colormap, moveOrientation, options);
                    break;
                default:
                    break;
            }


            this._alignedExtent = PlotHelper.GetAlignedExtent(options.AlignedExtentOptions, new CoordinateRect(0, fftLength, 0, framCount), out this._cellWidth, out this._cellHeight);
            //this._alignedExtent = this.GetAlignedExtent(options, new CoordinateRect(0, dataWidth, 0, dataHeight), out this._cellWidth, out this._cellHeight);
            this._axisLimits = new AxisLimits(this._alignedExtent);
            this._moveOrientation = moveOrientation;
        }

        /*
      private CoordinateRect GetAlignedExtent(WaterfallPlotOptions options, CoordinateRect extentOrDefault, out double cellWidth, out double cellHeight)
      {
          CoordinateRect? originPosition = options.Position;
          if (options.CellWidth.HasValue || options.CellHeight.HasValue)
          {
              double left = extentOrDefault.Left;
              double bottom = extentOrDefault.Bottom;
              double right, top;

              if (options.CellWidth.HasValue)
              {
                  right = extentOrDefault.Left + options.CellWidth.Value * this._dataWidth;
              }
              else
              {
                  right = extentOrDefault.Right;
              }

              if (options.CellHeight.HasValue)
              {
                  top = extentOrDefault.Bottom + options.CellHeight.Value * this._dataHeight;
              }
              else
              {
                  top = extentOrDefault.Top;
              }
              originPosition = new CoordinateRect(left, right, bottom, top);
          }

          if (originPosition.HasValue)
          {
              CoordinateRect position = originPosition.Value;
              //user will provide the extends to the data. The image will be one cell wider and taller so we need to add that on (it is being added on in teh default case).
              double cellwidth = position.Width / (this._dataWidth - 1);
              double cellheight = position.Height / (this._dataHeight - 1);

              if (position.Left < position.Right)
              {
                  position.Right += cellwidth;
              }

              if (position.Left > position.Right)
              {
                  position.Left -= cellwidth; //cellwidth will be negative if extent is flipped
              }

              if (position.Bottom < position.Top)
              {
                  position.Top += cellheight;
              }

              if (position.Bottom > position.Top)
              {
                  position.Bottom -= cellheight; //cellheight will be negative if extent is inverted
              }

              extentOrDefault = position;
          }

          cellWidth = extentOrDefault.Width / this._dataWidth;
          cellHeight = extentOrDefault.Height / this._dataHeight;
          double xOffset = Math.Abs(cellWidth) * options.CellAlignment.HorizontalFraction();
          double yOffset = Math.Abs(cellHeight) * options.CellAlignment.VerticalFraction();
          var cellOffset = new Coordinates(-xOffset, -yOffset);
          return extentOrDefault.WithTranslation(cellOffset);
      }*/




        #region IPlottable

        private bool _isVisible = true;
        /// <summary>
        /// 是否可见
        /// </summary>
        public bool IsVisible
        {
            get { return this._isVisible; }
            set { this._isVisible = value; }
        }


        private IAxes _axes = new Axes();
        /// <summary>
        /// 获取或设置坐标系
        /// </summary>
        public IAxes Axes
        {
            get { return this._axes; }
            set { this._axes = value; }
        }

        private AxisLimits _axisLimits;
        /// <summary>
        /// 获取坐标范围
        /// </summary>
        /// <returns></returns>
        public AxisLimits GetAxisLimits()
        {
            return this._axisLimits;
        }

        /// <summary>
        /// 渲染图
        /// </summary>
        /// <param name="rp"></param>
        public void Render(RenderPack rp)
        {
            if (this._waterfallPlot == null || !this._isVisible)
            {
                return;
            }

            SKRect rect = this._axes.GetPixelRect(this._alignedExtent).ToSKRect();
            this._waterfallPlot.Render(rp.Canvas, rect);
        }

        /// <summary>
        /// 
        /// </summary>
        public IEnumerable<LegendItem> LegendItems
        {
            get { return Enumerable.Empty<LegendItem>(); }
        }

        #endregion



        /// <summary>
        /// 追加一帧数据
        /// </summary>
        /// <param name="specData">频谱数据</param>
        public void AppendData(double[] specData)
        {
            if (this._waterfallPlot == null)
            {
                throw new InvalidOperationException("未初始化设置.");
            }

            if (specData == null || specData.Length < this._fftLength)
            {
                return;
            }

            fixed (double* data = specData)
            {
                this.AppendData(new Span<double>(specData, 0, specData.Length));
            }
        }

        /// <summary>
        /// 追加一帧数据
        /// </summary>
        /// <param name="specData">频谱数据</param>
        public void AppendData(Span<double> specData)
        {
            if (this._waterfallPlot == null || specData.Length < this._fftLength)
            {
                throw new InvalidOperationException("未初始化设置.");
            }

            if (specData.Length > this._fftLength)
            {
                specData = specData.Slice(0, this._fftLength);
            }

            this._waterfallPlot.AppendData(specData);
        }

        /// <summary>
        /// 追加连续的多帧数据
        /// </summary>
        /// <param name="specData">多行频谱数据</param>
        public void AppendDatas(double[] specData)
        {
            if (this._waterfallPlot == null || specData.Length < this._fftLength)
            {
                throw new InvalidOperationException("未初始化设置.");
            }

            fixed (double* data = specData)
            {
                this.AppendDatas(new Span<double>(specData, 0, specData.Length));
            }
        }

        /// <summary>
        /// 追加连续的多帧数据
        /// </summary>
        /// <param name="specData">多行频谱数据</param>
        public void AppendDatas(Span<double> specData)
        {
            if (this._waterfallPlot == null || specData.Length < this._fftLength)
            {
                throw new InvalidOperationException("未初始化设置.");
            }

            int preAppendFramCount = specData.Length / this._fftLength;
            int preAppendDataLen = preAppendFramCount * this._fftLength;

            if (preAppendDataLen != specData.Length)
            {
                //非fft长度的余数去掉
                specData = specData.Slice(0, preAppendDataLen);
            }

            if (preAppendFramCount > this._framCount)
            {
                //总长超出画布数据长,截取最后一截数据
                specData = specData.Slice((preAppendFramCount - this._framCount) * this._fftLength, this._imgPixs);
                preAppendFramCount = this._framCount;
            }

            this._waterfallPlot.AppendDatas(specData, preAppendFramCount);
        }

        /// <summary>
        /// 获取图上指定坐标的值
        /// </summary>
        /// <param name="coordinates">坐标</param>
        /// <returns>值</returns>
        public double GetValue(Coordinates coordinates)
        {
            if (this._waterfallPlot == null)
            {
                throw new InvalidOperationException("未初始化设置.");
            }

            CoordinateRect rect = this._alignedExtent;
            if (!rect.Contains(coordinates))
            {
                return double.NaN;
            }

            double distanceFromLeft = coordinates.X - rect.Left;
            int xIndex = (int)(distanceFromLeft / this._cellWidth);

            double distanceFromTop = rect.Top - coordinates.Y;
            int yIndex = (int)(distanceFromTop / this._cellHeight);

            return this._waterfallPlot.GetValue(xIndex, yIndex);
        }

    }

}
